#include "ST7735.h"
#include <limits.h>
#include "pins_arduino.h"
#include "wiring_private.h"
#include <SPI.h>

#define SPI_DEFAULT_FREQ 32000000 ///< Default SPI data clock frequency
// CONSTRUCTORS ************************************************************

/*!
    @brief  Instantiate Adafruit ST7735 driver with default hardware SPI
    @param  cs   Chip select pin #
    @param  dc   Data/Command pin #
    @param  rst  Reset pin # (optional, pass -1 if unused)
*/
ST7735::ST7735(SPIClass *spiClass,int8_t cs, int8_t dc, int8_t rst)
    : Adafruit_SPITFT(ST7735_TFTWIDTH_80, ST7735_TFTHEIGHT_160, spiClass, cs, dc, rst) {}


// clang-format off
static const uint8_t PROGMEM
  Rcmd1[] = {                       // 7735R ini
    21,                             // 21 commands in list:
    ST77XX_SWRESET,   ST_CMD_DELAY, //  1: Software reset, 0 args, w/delay
      150,                          //     150 ms delay
    ST77XX_SLPOUT,    ST_CMD_DELAY, //  2: Out of sleep mode, 0 args, w/delay
      150,                          //     500 ms delay
    ST7735_FRMCTR1, 3,              //  3: Framerate ctrl - normal mode, 3 arg:
      0x01, 0x2C, 0x2D,             //     Rate = fosc/(1x2+40) * (LINE+2C+2D)
    ST7735_FRMCTR2, 3,              //  4: Framerate ctrl - idle mode, 3 args:
      0x01, 0x2C, 0x2D,             //     Rate = fosc/(1x2+40) * (LINE+2C+2D)
    ST7735_FRMCTR3, 6,              //  5: Framerate - partial mode, 6 args:
      0x01, 0x2C, 0x2D,             //     Dot inversion mode
      0x01, 0x2C, 0x2D,             //     Line inversion mode
    ST7735_INVCTR,  1,              //  6: Display inversion ctrl, 1 arg:
      0x07,                         //     No inversion
    ST7735_PWCTR1,  3,              //  7: Power control, 3 args, no delay:
      0xA2,
      0x02,                         //     -4.6V
      0x84,                         //     AUTO mode
    ST7735_PWCTR2,  1,              //  8: Power control, 1 arg, no delay:
      0xC5,                         //     VGH25=2.4C VGSEL=-10 VGH=3 * AVDD
    ST7735_PWCTR3,  2,              //  9: Power control, 2 args, no delay:
      0x0A,                         //     Opamp current small
      0x00,                         //     Boost frequency
    ST7735_PWCTR4,  2,              // 10: Power control, 2 args, no delay:
      0x8A,                         //     BCLK/2,
      0x2A,                         //     opamp current small & medium low
    ST7735_PWCTR5,  2,              // 11: Power control, 2 args, no delay:
      0x8A, 0xEE,
    ST7735_VMCTR1,  1,              // 12: Power control, 1 arg, no delay:
      0x0E,
    ST77XX_INVOFF,  0,              // 13: Don't invert display, no args
    ST77XX_MADCTL,  1,              // 14: Mem access ctl (directions), 1 arg:
      0xC8,                         //     row/col addr, bottom-top refresh
    ST77XX_COLMOD,  1,              // 15: set color mode, 1 arg, no delay:
      0x05 ,
    ST77XX_CASET,   4,              // 16: Column addr set, 4 args, no delay:
      0x00, 0x00,                   //     XSTART = 0
      0x00, 0x4F,                   //     XEND = 79
    ST77XX_RASET,   4,              // 17: Row addr set, 4 args, no delay:
      0x00, 0x00,                   //     XSTART = 0
      0x00, 0x9F,  
    ST7735_GMCTRP1, 16      ,       // 18: Gamma Adjustments (pos. polarity), 16 args + delay:
      0x02, 0x1c, 0x07, 0x12,       //     (Not entirely necessary, but provides
      0x37, 0x32, 0x29, 0x2d,       //      accurate colors)
      0x29, 0x25, 0x2B, 0x39,
      0x00, 0x01, 0x03, 0x10,
    ST7735_GMCTRN1, 16      ,       // 19: Gamma Adjustments (neg. polarity), 16 args + delay:
      0x03, 0x1d, 0x07, 0x06,       //     (Not entirely necessary, but provides
      0x2E, 0x2C, 0x29, 0x2D,       //      accurate colors)
      0x2E, 0x2E, 0x37, 0x3F,
      0x00, 0x00, 0x02, 0x10,
    ST77XX_NORON,     ST_CMD_DELAY, // 20: Normal display on, no args, w/delay
      10,                           //     10 ms delay
    ST77XX_DISPON,    ST_CMD_DELAY, // 21: Main screen turn on, no args w/delay
      100 },                        //     100 ms delay

  RcmdGC9106[] = {                       // GC9106 
    29,                             // 15 commands in list:
    ST77XX_SWRESET,   ST_CMD_DELAY, //  1: Software reset, 0 args, w/delay
      150,    
    ST77XX_DISPOFF,  0,
    0xfe, 0,       //GC9106_ENAB1
    0xfe, 0, 
    0xfe, 0,
    0xef, 0,       //GC9106_ENAB2
    0xb3, 1, 0x03, //GC9106_ACCESS_F0_F1
    0x36, 1, 0xd8, //USER_MADCTL
    0x3a, 1, 0x05, //USER_COLMOD
    0xb6, 1, 0x11, //GC9106_ACCESS_A3_AA_AC
    0xac, 1, 0x0b, //undocumented
    0xb4, 1, 0x21, //GC9106_INVCTR
    0xb0, 1, 0x00, //GC9106_ACCESS_C0_C1_C2_C3_C6
    0xb2, 1, 0x00, //GC9106_ACCESS_E4_EB
    0xb1, 1, 0xc0, //GC9106_ACCESS_E6_E7
    0xe6, 2, 0x50, 0x43, //GC9106_VREG1 [50 43]
    0xe7, 2, 0x56, 0x43, //GC9106_VREG2 [38 43]
    0xF0, 14, 0x1f, 0x41, 0x1B, 0x55, 0x36, 0x3d, 0x3e, 0x0, 0x16, 0x08, 0x09, 0x15, 0x14, 0xf,
    0xF1, 14, 0x1f, 0x41, 0x1B, 0x55, 0x36, 0x3d, 0x3e, 0x0, 0x16, 0x08, 0x09, 0x15, 0x14, 0xf,
    0xfe, 0,       //GC9106_ENAB1
    0xff, 0,       //???
    0x35, 1, 0x00, //USER_TEON
    0x44, 1, 0x00, //GC9106_SETSCANLINE
	  0x11,	ST_CMD_DELAY, 150,//USER_SLPOUT
    0x29, 0,       //USER_DISPON
    0x2A, 4, /***Set Column Address***/ 0x00, 0x18, 0x00, 0x67,
    0x2B, 4, /***Set Page Address***/ 0x00, 0x00, 0x00, 0x9f,
    //(0x2c), 0,       //USER_MEMWR
    0x11, ST_CMD_DELAY, 150,// Exit Sleep, then delay 150 ms
    0x29, ST_CMD_DELAY, 100 // Main screen turn on, delay 150 ms
  }; 


/**************************************************************************/
/*!
    @brief  Companion code to the initiliazation tables. Reads and issues
            a series of LCD commands stored in PROGMEM byte array.
    @param  addr  Flash memory array with commands and data to send
*/
/**************************************************************************/
void ST7735::displayInit(const uint8_t *addr) {

  uint8_t numCommands, cmd, numArgs;
  uint16_t ms;

  numCommands = pgm_read_byte(addr++); // Number of commands to follow
  while (numCommands--) {              // For each command...
    cmd = pgm_read_byte(addr++);       // Read command
    numArgs = pgm_read_byte(addr++);   // Number of args to follow
    ms = numArgs & ST_CMD_DELAY;       // If hibit set, delay follows args
    numArgs &= ~ST_CMD_DELAY;          // Mask out delay bit
    sendCommand(cmd, addr, numArgs);
    addr += numArgs;

    if (ms) {
      ms = pgm_read_byte(addr++); // Read post-command delay time (ms)
      if (ms == 255)
        ms = 500; // If 255, delay for 500 ms
      delay(ms);
    }
  }
}

/**************************************************************************/
/*!
    @brief  Initialize ST77xx chip. Connects to the ST77XX over SPI and
            sends initialization procedure commands
    @param  freq  Desired SPI clock frequency
*/
/**************************************************************************/
void ST7735::begin(uint32_t freq) {
  if (!freq) {
    freq = SPI_DEFAULT_FREQ;
  }
  _freq = freq;

  invertOnCommand = ST77XX_INVON;
  invertOffCommand = ST77XX_INVOFF;

  initSPI(freq, spiMode);
}

/**************************************************************************/
/*!
    @brief  Initialization code common to all ST77XX displays
    @param  cmdList  Flash memory array with commands and data to send
*/
/**************************************************************************/
void ST7735::commonInit(const uint8_t *cmdList) {
  begin();

  if (cmdList) {
    displayInit(cmdList);
  }
}

/**************************************************************************/
/*!
  @brief  SPI displays set an address window rectangle for blitting pixels
  @param  x  Top left corner x coordinate
  @param  y  Top left corner y coordinate
  @param  w  Width of window
  @param  h  Height of window
*/
/**************************************************************************/
void ST7735::setAddrWindow(uint16_t x, uint16_t y, uint16_t w,
                                    uint16_t h) {
  x += _xstart;
  y += _ystart;
  uint32_t xa = ((uint32_t)x << 16) | (x + w - 1);
  uint32_t ya = ((uint32_t)y << 16) | (y + h - 1);

  writeCommand(ST77XX_CASET); // Column addr set
  SPI_WRITE32(xa);

  writeCommand(ST77XX_RASET); // Row addr set
  SPI_WRITE32(ya);

  writeCommand(ST77XX_RAMWR); // write to RAM
}


/**************************************************************************/
/*!
    @brief  Initialization code common to all ST7735R displays
    @param  options  Tab color from adafruit purchase
*/
/**************************************************************************/
void ST7735::initR(uint8_t options) {
  _height = ST7735_TFTWIDTH_80;
  _width = ST7735_TFTHEIGHT_160;
  if(options==INITR_MINI160x80_GC9106){
    commonInit(RcmdGC9106);
    _colstart = 24;
    _rowstart = 0;
  }
  else if (options == INITR_MINI160x80) {
      commonInit(Rcmd1);
      _colstart = 24;
      _rowstart = 0;
  } else if (options == INITR_MINI160x80_PLUGIN) {
    commonInit(Rcmd1);
    _colstart = 26;
    _rowstart = 1;
  } 

    _width = ST7735_TFTHEIGHT_160;
    _height = ST7735_TFTWIDTH_80;
    _ystart = _colstart;
    _xstart = _rowstart;
}

// OTHER FUNCTIONS *********************************************************

/**************************************************************************/
/*!
    @brief  Set origin of (0,0) of display with offsets
    @param  col  The offset from 0 for the column address
    @param  row  The offset from 0 for the row address
*/
/**************************************************************************/
void ST7735::setColRowStart(int8_t col, int8_t row) {
  _colstart = col;
  _rowstart = row;
}

/**************************************************************************/
/*!
 @brief  Change whether display is on or off
 @param  enable True if you want the display ON, false OFF
 */
/**************************************************************************/
void ST7735::enableDisplay(boolean enable) {
  sendCommand(enable ? ST77XX_DISPON : ST77XX_DISPOFF);
}

/**************************************************************************/
/*!
 @brief  Change whether TE pin output is on or off
 @param  enable True if you want the TE pin ON, false OFF
 */
/**************************************************************************/
void ST7735::enableTearing(boolean enable) {
  sendCommand(enable ? ST77XX_TEON : ST77XX_TEOFF);
}

/**************************************************************************/
/*!
 @brief  Change whether sleep mode is on or off
 @param  enable True if you want sleep mode ON, false OFF
 */
/**************************************************************************/
void ST7735::enableSleep(boolean enable) {
  sendCommand(enable ? ST77XX_SLPIN : ST77XX_SLPOUT);
}
